<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>星级评分原理和实现--第二种实现方式</title>
    <style>
        body { background-color:#00346e; }
        body { padding:0; margin:0; }
        .rating { width:325px; height:65px; margin: 100px auto; }
        .rating_item { float: left; width:65px; height:65px; cursor: pointer; }
    </style>
</head>
<body>
    <div id="rating" class="rating">
        <img src="img/star_off.jpg" alt="星星" class="rating_item" title="很不好">
        <img src="img/star_off.jpg" alt="星星" class="rating_item" title="不好">
        <img src="img/star_off.jpg" alt="星星" class="rating_item" title="一般">
        <img src="img/star_off.jpg" alt="星星" class="rating_item" title="好">
        <img src="img/star_off.jpg" alt="星星" class="rating_item" title="很好">
    </div>

    <div id="rating2" class="rating">
        <img src="img/star_off.jpg" alt="星星" class="rating_item" title="很不好">
        <img src="img/star_off.jpg" alt="星星" class="rating_item" title="不好">
        <img src="img/star_off.jpg" alt="星星" class="rating_item" title="一般">
        <img src="img/star_off.jpg" alt="星星" class="rating_item" title="好">
        <img src="img/star_off.jpg" alt="星星" class="rating_item" title="很好">
    </div>

    <script src="js/jquery-3.2.1.min.js"></script>
    <script>

        //减少暴露全局变量，引入局部作用域
        var rating = (function() {
            //继承
            var extend = function(subClass,superClass) {
                var F = function() {};                          // 创建一个空的临时的函数
                F.prototype = superClass.prototype;             //父类原型赋值给F原型
                subClass.prototype = new F();                   //实例化new F()
                subClass.prototype.construtor = subClass;       //constructor 属性返回对创建此对象的数组函数的引用
            };

            //点亮
            var Light = function(el, options) {
                this.$el = $(el);   //变成jQuery对象
                this.$item = this.$el.find('.rating_item');
                this.opts = options;
                this.add = 1;
                this.selectEvent = 'mouseover';
            };

            //初始化方法，一般都是：将方法写在构造函数的原型上，这样写可以保证不管你实例化多少次，我们内存中都只有一份
            Light.prototype.init = function() {
                this.lightOn(this.opts.num);        //点亮几颗星星

                if(!this.opts.readOnly) {           //判断是否只读
                    this.bindEvent();               //绑定事件
                }
                
            };

            Light.prototype.lightOn = function(num) {
                num = parseInt(num);                //转化成整数

                this.$item.each(function(index){
                    if(index < num) {
                        $(this).attr('src','img/star_on.jpg');
                    } else {
                        $(this).attr('src','img/star_off.jpg');
                    }
                });
            };

            Light.prototype.bindEvent = function() {
                var self = this,
                    itemLength = self.$item.length;

                self.$el.on(self.selectEvent, '.rating_item', function(e) {
                    var $this = $(this),
                        num = 0;

                    self.select(e,$this);
                    num = $this.index() + self.add;
                    self.lightOn( num );

                    (typeof self.opts.select === 'function') && self.opts.select.call( this, num, itemLength );             //判断select是否为函数

                    self.$el.trigger('select',[num,itemLength]);            // 触发事件

                }).on('click', '.rating_item', function() {
                    self.opts.num = $(this).index() + self.add;

                    (typeof self.opts.chosen === 'function') && self.opts.chosen.call( this, self.opts.num, itemLength );   //判断select是否为函数

                    
                    self.$el.trigger('chosen',[self.opts.num,itemLength]);  // 触发事件

                }).on('mouseout',function() {
                    self.lightOn(self.opts.num);
                });
            };

            Light.prototype.select = function() {
                throw new Error('子类必须重写此方法');
            };
            Light.prototype.unbindEvent = function() {
                this.$el.off();             //off() 方法通常用于移除通过 on() 方法添加的事件处理程序
            };


            //点亮整颗
            var LightEntire = function(el, options) {
                Light.call(this,el,options);
                this.selectEvent = 'mouseover';
            };

            extend(LightEntire,Light);

            LightEntire.prototype.lightOn = function(num) {
                Light.prototype.lightOn.call(this,num);
            };

            LightEntire.prototype.select = function() {
                self.add = 1;
            };


            //点亮半颗
            var LightHalf = function(el, options) {
                Light.call(this,el,options);
                this.selectEvent = 'mousemove';
            };

            extend(LightHalf,Light);

            LightHalf.prototype.lightOn = function(num) {
                var count = parseInt(num),
                    isHalf = count !== num;     //判断是否为半颗

                Light.prototype.lightOn.call(this,count);

                if(isHalf) {
                    this.$item.eq(count).attr('src','img/star_half.jpg');
                };

            };

            LightHalf.prototype.select = function(e,$this) {
                if(e.pageX - $this.offset().left < $this.width()/2) {   //半颗
                    this.add = 0.5;
                } else{                                                 //整颗
                    this.add = 1;
                };
            };

            // 默认参数
            var defaults = {
                mode:'LightEntire',     //模式默认整颗
                num:0,                  //默认点亮的星星个数
                readOnly:false,         //是否自读
                select:function() {},   //鼠标每次移到星星的时候执行的方法
                chosen:function() {}    //点击星星的时候
            }

            var mode = {                //上面参数mode是一个字符串，我们需要点亮的它应该是个对象，因此需要做一个映射
                'LightEntire':LightEntire,
                'LightHalf':LightHalf,
            };

            //初始化
            var init = function(el,option) {
                var $el = $(el),        //变成jQuery对象
                    rating = $el.data('rating'),
                    options = $.extend({}, defaults, typeof option === 'object' && option);         // $.extend() 函数用于将一个或多个对象的内容合并到目标对象,这里是用options的内容来覆盖defaults的内容，生成的内容放到{}这个空对象中
                    //判断option是否为对象，如果是，使用可以使用option，如果否，返回false，直接把defaults的值返回给options

                if(!mode[options.mode]) {                          //容错机制
                    options.mode = 'LightEntire';                  //设置默认为整颗
                };
            
                // new LightEntire(el,options).init();             //实例化LightEntire(),调用init()方法
                if(!rating) {               //判断rating是否存在，如果否，重新声明加执行
                    $el.data('rating',rating = new mode[options.mode](el,options))    //data() 方法向被选元素附加数据，或者从被选元素获取数据。
                    rating.init();         //实例化mode
                };
                if(typeof option === 'string') rating[option]();    //判断是否为字符串，如果是，则直接执行字符串相应的方法
            };

            //jQuery插件
            $.fn.extend({
                rating:function(option) {
                    return this.each(function() {
                        init(this,option)
                    });
                }
            });

            return {                        // 返回对象，有返回就需要在外部引入一个全局变量来接受它，就是var rating
                init:init
            };
        })();

        //jQuery调用
        $('#rating').rating({
            mode:'LightEntire',
            num:2,
        });

        $('#rating2').rating({
            mode:'LightHalf',
            num:2.5,
        });
        $('#rating2').on('chosen',function() {      //绑定事件
            $('#rating2').rating('unbindEvent');
        });

    </script>
</body>
</html>